{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "uXIFeTlkgLlG"
   },
   "source": [
    "**!!! ДАННЫЙ НОУТБУК ПРЕДНАЗНАЧЕН ТОЛЬКО ДЛЯ ЗАПУСКА В GOOGLE COLAB !!!**\n",
    "\n",
    "# Детекция лиц в магазине (результаты)\n",
    "\n",
    "# 1 Постановка задачи\n",
    "\n",
    "Есть запись с видеокамеры внутри магазина. Требуется детектировать лица людей, которые находятся в пределах магазина. За пределами магазина детектор не должен срабатывать.\n",
    "\n",
    "Написать скрипт, который на вход принимает файл с видео, выполняет детекцию и сохраняет видео с детекцией в отдельный файл.\n",
    "\n",
    "Дополнительно необходимо считать уникальных людей, которые посетили магазин.\n",
    "\n",
    "Ссылка на исходное видео:\n",
    "https://drive.google.com/file/d/1tHsf2gosdpGGaBtBJRj7cPJAr3eTloD0\n",
    "\n",
    "## 1.1 Возможные пути решения задачи\n",
    "\n",
    "### 1.1.1 Определение расстояния от камеры до объекта с помощью карты глубины изображения (depth map)\n",
    "\n",
    "Для данного пути требуется стереоизображение. Например, можно установить 2 одинаковых по техническим характеристикам веб-камеры с небольшим расстоянием друг от друга, и направить их в одну точку. В результате с помощью двух записей под разным углом можно вычислить карту глубины изображения. С помощью данной карты можно определить расстояние объекта до камеры.\n",
    "\n",
    "В данной задаче запись ведется только с одной камеры, поэтому данным путем задачу решить не получится.\n",
    "\n",
    "### 1.1.2 Определение расстояния от камеры до объекта с помощью размера бокса детекции лица на изображении\n",
    "\n",
    "В данном случае предполагаем, что чем дальше человек от камеры, тем меньше будут размеры бокса детекции его лица. Можно считать площадь бокса. Если площадь меньше определенного порога, то такие боксы можно удалять. Такой подход можно применить к уже обученным детекторам.\n",
    "\n",
    "В случае изменения расположения камеры будет достаточным поменять параметр, отвечающий за фильтрацию по площади. Так же для данного пути можно обучить детектор с нуля, взяв готовый большой датасет с разметкой лиц людей и добавить к нему изображения с камеры магазина с ручной разметкой. Так же важно учитывать мировую ситуацию с пандемией, люди ходят в масках. Требуется много примеров лиц с маской.\n",
    "\n",
    "### 1.1.3 Определение человека в магазине и за его пределами с помощью обучения детектора с механизмом классификации\n",
    "\n",
    "В этом случае можно обучить детектор находить лица людей и определять находится ли человек в магазине или за его пределами. Для этого требуется обучить детектор на изображениях с камеры магазина. И выполнить ручную разметку лиц 2 классами: IN (в магазине), OUT (вне магазина).\n",
    "\n",
    "Данный подход требует большого количества примеров изображений для своей корректной работы. Также данный метод перестанет работать, если изменить ракурс камеры или переставить ее в другое место. Потребуется переобучение модели.\n",
    "\n",
    "### 1.1.4 Ансамбль моделей\n",
    "\n",
    "В данном подходе можно комбинировать разные модели между собой.\n",
    "Например:\n",
    "- предобученный детектор для детекции лиц;\n",
    "- обученный с нуля детектор на основе подхода определения расстояния от камеры с помощью размера бокса детекции лица;\n",
    "- обученный с нуля детектор на основе подхода человека в магазине и за его пределами с помощью обучения детектора с механизмом классификации;\n",
    "\n",
    "Результаты работы моделей можно объединять в помощью механизма NMS или более прогрессивного Weighted Boxes Fusion.\n",
    "\n",
    "# 2 Предложенное решение\n",
    "\n",
    "В данном решении будет использована ансамблевая модель. Взяты следующие модели:\n",
    "\n",
    "- предобученная модель детекции лиц MTCNN из фреймворка facenet (базовая модель)\n",
    "- обученный с нуля детектор с механизмом классификации на основе архитектуры VarifocalNet из фреймворка MMDetection.\n",
    "\n",
    "Ноутбук с обучением детектора можно найти здесь:\n",
    "https://github.com/inject0r1945/ds_projects/blob/main/CV/shop_face_detection/train_vfnet_detector.ipynb\n",
    "\n",
    "Модель MTCNN предобучена на большом датасете лиц и может работать как универсальный детектор. Но в видео для данной задачи не всегда определяет лица людей по следующим предполагаемым причинам:\n",
    "- много людей в масках, модель не обучалась на таких лицах;\n",
    "- качество изображения среднее, много темных тонов и недостаточно контрастности.\n",
    "\n",
    "Для улучшения базовой модели принято решение обучить с нуля детектор архитектуры VarifocalNet и объединить его результаты с базовой моделью. \n",
    "\n",
    "Для этой задачи были отобраны кадры из видео для обучения и выполнена ручная разметка лиц с помощью сервиса makesense.ai. Выделялись лица как за пределами магазина, так и в магазине. Детектор был обучен на классификацию людей, как было описано в пункте 1.1.3. В обучении использовалось около 140 изображений.\n",
    "\n",
    "Результаты базовой модели и дополнительной объединяются с помощью Weighted Boxes Fusion.\n",
    "\n",
    "Базовая модель фильтруется по площади бокса. Дополнительная модель умеет определять лица в магазине, но для подстраховки задана минимальная фильтрация по площади. На новых данных дополнительная модель может не находить лица, так как для ее обучения требуется больше примеров с людьми в магазине.\n",
    "\n",
    "Дополнительную модель можно обучать с помощью KFold-разбиения данных. В данной работе датасет был разбит на 3 фолда и в ансамбле дополнительная модель берется только для 1-го фолда для упрощения работы. \n",
    "\n",
    "Подсчет уникальных посетителей в данной работе не осущесвляется и предлагается только в виде следующего алгоритма действий:\n",
    "- создаем базу данных и в отдельной таблице храним уникальные боксы лиц человека в виде эмбединга, которому автоматически присваивается целочисленный идентификатор;\n",
    "- перед добавлением нового лица в таблицу существующих эмбедингов надо убедиться, что человек в ней отсутствует с помощью сравнения по метрики близости;\n",
    "- в отдельной таблице храним связь даты посещения с идентификатором ембединга лица человека;\n",
    "- дату посещения получаем из кадра видео во время детекции с помощью OCR;\n",
    "- перед добавлением новой связи лица человека с датой, добавляем эмбединг лица в общую таблицу эмбедингов с указаными вышепроверками;\n",
    "- для ведения базы берем детекции только внутри магазина, таким образом если сработала модель детекции, то считаем человеку посещение;\n",
    "- чтобы засчитать человеку только одно посещение, требуется сравнить его ембединг лица со всему другими ембедингами лиц людей на дату детекции: если похожие люди не найдены, значит добавляем новую связь ембединга с датой, иначе ничего не пишем в базу.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "0VSspGs8quNn"
   },
   "source": [
    "# 3 Подготовка к работе"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "background_save": true,
     "base_uri": "https://localhost:8080/"
    },
    "id": "lfPXUK1YgKJU",
    "outputId": "058d9c54-5a31-4bba-eec3-968afb8554a1"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: facenet-pytorch in /usr/local/lib/python3.7/dist-packages (2.5.2)\n",
      "Requirement already satisfied: torchvision in /usr/local/lib/python3.7/dist-packages (from facenet-pytorch) (0.6.1+cu101)\n",
      "Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from facenet-pytorch) (2.23.0)\n",
      "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from facenet-pytorch) (1.19.5)\n",
      "Requirement already satisfied: pillow in /usr/local/lib/python3.7/dist-packages (from facenet-pytorch) (7.0.0)\n",
      "Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->facenet-pytorch) (2.10)\n",
      "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->facenet-pytorch) (3.0.4)\n",
      "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->facenet-pytorch) (1.24.3)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->facenet-pytorch) (2021.10.8)\n",
      "Requirement already satisfied: torch==1.5.1 in /usr/local/lib/python3.7/dist-packages (from torchvision->facenet-pytorch) (1.5.1+cu101)\n",
      "Requirement already satisfied: future in /usr/local/lib/python3.7/dist-packages (from torch==1.5.1->torchvision->facenet-pytorch) (0.16.0)\n",
      "Requirement already satisfied: ensemble-boxes in /usr/local/lib/python3.7/dist-packages (1.0.8)\n",
      "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from ensemble-boxes) (1.19.5)\n",
      "Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from ensemble-boxes) (1.3.5)\n",
      "Requirement already satisfied: numba in /usr/local/lib/python3.7/dist-packages (from ensemble-boxes) (0.51.2)\n",
      "Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from numba->ensemble-boxes) (57.4.0)\n",
      "Requirement already satisfied: llvmlite<0.35,>=0.34.0.dev0 in /usr/local/lib/python3.7/dist-packages (from numba->ensemble-boxes) (0.34.0)\n",
      "Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas->ensemble-boxes) (2.8.2)\n",
      "Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/dist-packages (from pandas->ensemble-boxes) (2018.9)\n",
      "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.7.3->pandas->ensemble-boxes) (1.15.0)\n"
     ]
    }
   ],
   "source": [
    "!pip install facenet-pytorch\n",
    "!pip install ensemble-boxes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "lFVbeDCrI6GW"
   },
   "source": [
    "## 3.1 Блок установки компонентов MMDetection\n",
    "\n",
    "Выполняется до 40 минут."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Ejh4SYzOP6wP",
    "outputId": "3130998e-9eb8-41c1-83ce-da7520fc457a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "nvcc: NVIDIA (R) Cuda compiler driver\n",
      "Copyright (c) 2005-2020 NVIDIA Corporation\n",
      "Built on Mon_Oct_12_20:09:46_PDT_2020\n",
      "Cuda compilation tools, release 11.1, V11.1.105\n",
      "Build cuda_11.1.TC455_06.29190527_0\n",
      "gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0\n",
      "Copyright (C) 2017 Free Software Foundation, Inc.\n",
      "This is free software; see the source for copying conditions.  There is NO\n",
      "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Check nvcc version\n",
    "!nvcc -V\n",
    "# Check GCC version\n",
    "!gcc --version"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Mhxl9hokQBMk",
    "outputId": "1b4c28d7-aaec-4f30-8394-7b2271c71c58"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in links: https://download.pytorch.org/whl/torch_stable.html\n",
      "Requirement already satisfied: torch==1.5.1+cu101 in /usr/local/lib/python3.7/dist-packages (1.5.1+cu101)\n",
      "Requirement already satisfied: torchvision==0.6.1+cu101 in /usr/local/lib/python3.7/dist-packages (0.6.1+cu101)\n",
      "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from torch==1.5.1+cu101) (1.19.5)\n",
      "Requirement already satisfied: future in /usr/local/lib/python3.7/dist-packages (from torch==1.5.1+cu101) (0.16.0)\n",
      "Requirement already satisfied: pillow>=4.1.1 in /usr/local/lib/python3.7/dist-packages (from torchvision==0.6.1+cu101) (7.0.0)\n",
      "Requirement already satisfied: mmcv-full in /usr/local/lib/python3.7/dist-packages (1.4.4)\n",
      "Requirement already satisfied: pyyaml in /usr/local/lib/python3.7/dist-packages (from mmcv-full) (3.13)\n",
      "Requirement already satisfied: Pillow in /usr/local/lib/python3.7/dist-packages (from mmcv-full) (7.0.0)\n",
      "Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from mmcv-full) (21.3)\n",
      "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from mmcv-full) (1.19.5)\n",
      "Requirement already satisfied: addict in /usr/local/lib/python3.7/dist-packages (from mmcv-full) (2.4.0)\n",
      "Requirement already satisfied: yapf in /usr/local/lib/python3.7/dist-packages (from mmcv-full) (0.32.0)\n",
      "Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from packaging->mmcv-full) (3.0.7)\n",
      "Cloning into 'mmdetection'...\n",
      "remote: Enumerating objects: 23075, done.\u001b[K\n",
      "remote: Total 23075 (delta 0), reused 0 (delta 0), pack-reused 23075\u001b[K\n",
      "Receiving objects: 100% (23075/23075), 25.84 MiB | 32.08 MiB/s, done.\n",
      "Resolving deltas: 100% (16157/16157), done.\n",
      "/content/mmdetection\n",
      "Obtaining file:///content/mmdetection\n",
      "Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/dist-packages (from mmdet==2.21.0) (3.2.2)\n",
      "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from mmdet==2.21.0) (1.19.5)\n",
      "Requirement already satisfied: pycocotools in /usr/local/lib/python3.7/dist-packages (from mmdet==2.21.0) (2.0.4)\n",
      "Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from mmdet==2.21.0) (1.15.0)\n",
      "Requirement already satisfied: terminaltables in /usr/local/lib/python3.7/dist-packages (from mmdet==2.21.0) (3.1.10)\n",
      "Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmdet==2.21.0) (1.3.2)\n",
      "Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmdet==2.21.0) (2.8.2)\n",
      "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmdet==2.21.0) (0.11.0)\n",
      "Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmdet==2.21.0) (3.0.7)\n",
      "Installing collected packages: mmdet\n",
      "  Running setup.py develop for mmdet\n",
      "Successfully installed mmdet-2.21.0\n",
      "Requirement already satisfied: Pillow==7.0.0 in /usr/local/lib/python3.7/dist-packages (7.0.0)\n"
     ]
    }
   ],
   "source": [
    "# install dependencies: (use cu101 because colab has CUDA 10.1)\n",
    "!pip install -U torch==1.5.1+cu101 torchvision==0.6.1+cu101 -f https://download.pytorch.org/whl/torch_stable.html\n",
    "\n",
    "# install mmcv-full thus we could use CUDA operators\n",
    "!pip install mmcv-full\n",
    "\n",
    "# Install mmdetection\n",
    "!rm -rf mmdetection\n",
    "!git clone https://github.com/open-mmlab/mmdetection.git\n",
    "%cd mmdetection\n",
    "\n",
    "!pip install -e .\n",
    "\n",
    "# install Pillow 7.0.0 back in order to avoid bug in colab\n",
    "!pip install Pillow==7.0.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "3LyTREoRYEWE",
    "outputId": "ffdc3777-334a-4b8d-8020-e1c23db02ffa"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.5.1+cu101 True\n",
      "2.21.0\n",
      "11.1\n",
      "GCC 7.5\n"
     ]
    }
   ],
   "source": [
    "# Check Pytorch installation\n",
    "import torch, torchvision\n",
    "print(torch.__version__, torch.cuda.is_available())\n",
    "\n",
    "# Check MMDetection installation\n",
    "import mmdet\n",
    "print(mmdet.__version__)\n",
    "\n",
    "# Check mmcv installation\n",
    "from mmcv.ops import get_compiling_cuda_version, get_compiler_version\n",
    "print(get_compiling_cuda_version())\n",
    "print(get_compiler_version())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "VzFI4SkfrOPL"
   },
   "source": [
    "Загрузка весов обученной с нуля модели детектора с архитектурой VarifocalNet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "SvqxMN1lrMPw",
    "outputId": "0d7e16c1-fe5c-46fb-e007-df7958b35d0b"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--2022-02-14 02:25:55--  http://wget/\n",
      "Resolving wget (wget)... failed: Name or service not known.\n",
      "wget: unable to resolve host address ‘wget’\n",
      "--2022-02-14 02:25:55--  https://docs.google.com/uc?export=download&confirm=OYQ1&id=1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq\n",
      "Resolving docs.google.com (docs.google.com)... 142.250.125.101, 142.250.125.138, 142.250.125.139, ...\n",
      "Connecting to docs.google.com (docs.google.com)|142.250.125.101|:443... connected.\n",
      "HTTP request sent, awaiting response... 302 Moved Temporarily\n",
      "Location: https://doc-04-68-docs.googleusercontent.com/docs/securesc/bbjvap6m2ivaphvnavns9ncjh413idc9/glbchtl69jjqcuf14df8vqal9igdiupi/1644805500000/15088970906101947359/04791134259099198641Z/1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq?e=download [following]\n",
      "--2022-02-14 02:25:55--  https://doc-04-68-docs.googleusercontent.com/docs/securesc/bbjvap6m2ivaphvnavns9ncjh413idc9/glbchtl69jjqcuf14df8vqal9igdiupi/1644805500000/15088970906101947359/04791134259099198641Z/1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq?e=download\n",
      "Resolving doc-04-68-docs.googleusercontent.com (doc-04-68-docs.googleusercontent.com)... 142.250.136.132, 2607:f8b0:4001:c34::84\n",
      "Connecting to doc-04-68-docs.googleusercontent.com (doc-04-68-docs.googleusercontent.com)|142.250.136.132|:443... connected.\n",
      "HTTP request sent, awaiting response... 302 Found\n",
      "Location: https://docs.google.com/nonceSigner?nonce=3nr5kjek124no&continue=https://doc-04-68-docs.googleusercontent.com/docs/securesc/bbjvap6m2ivaphvnavns9ncjh413idc9/glbchtl69jjqcuf14df8vqal9igdiupi/1644805500000/15088970906101947359/04791134259099198641Z/1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq?e%3Ddownload&hash=r78ooemc2edmmp8kkjuvefdnv06k6s9h [following]\n",
      "--2022-02-14 02:25:55--  https://docs.google.com/nonceSigner?nonce=3nr5kjek124no&continue=https://doc-04-68-docs.googleusercontent.com/docs/securesc/bbjvap6m2ivaphvnavns9ncjh413idc9/glbchtl69jjqcuf14df8vqal9igdiupi/1644805500000/15088970906101947359/04791134259099198641Z/1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq?e%3Ddownload&hash=r78ooemc2edmmp8kkjuvefdnv06k6s9h\n",
      "Connecting to docs.google.com (docs.google.com)|142.250.125.101|:443... connected.\n",
      "HTTP request sent, awaiting response... 302 Found\n",
      "Location: https://doc-04-68-docs.googleusercontent.com/docs/securesc/bbjvap6m2ivaphvnavns9ncjh413idc9/glbchtl69jjqcuf14df8vqal9igdiupi/1644805500000/15088970906101947359/04791134259099198641Z/1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq?e=download&nonce=3nr5kjek124no&user=04791134259099198641Z&hash=criidg883rpletnk18i0qll2ukgkvmr9 [following]\n",
      "--2022-02-14 02:25:55--  https://doc-04-68-docs.googleusercontent.com/docs/securesc/bbjvap6m2ivaphvnavns9ncjh413idc9/glbchtl69jjqcuf14df8vqal9igdiupi/1644805500000/15088970906101947359/04791134259099198641Z/1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq?e=download&nonce=3nr5kjek124no&user=04791134259099198641Z&hash=criidg883rpletnk18i0qll2ukgkvmr9\n",
      "Connecting to doc-04-68-docs.googleusercontent.com (doc-04-68-docs.googleusercontent.com)|142.250.136.132|:443... connected.\n",
      "HTTP request sent, awaiting response... 200 OK\n",
      "Length: 787036535 (751M) [application/octet-stream]\n",
      "Saving to: ‘checkpoints/vfnet_checkpoint.pth’\n",
      "\n",
      "checkpoints/vfnet_c 100%[===================>] 750.58M   314MB/s    in 2.4s    \n",
      "\n",
      "2022-02-14 02:25:58 (314 MB/s) - ‘checkpoints/vfnet_checkpoint.pth’ saved [787036535/787036535]\n",
      "\n",
      "FINISHED --2022-02-14 02:25:58--\n",
      "Total wall clock time: 2.7s\n",
      "Downloaded: 1 files, 751M in 2.4s (314 MB/s)\n"
     ]
    }
   ],
   "source": [
    "!mkdir checkpoints\n",
    "!wget wget --load-cookies /tmp/cookies.txt \"https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate \\\n",
    "           'https://docs.google.com/uc?export=download&id=1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\\1\\n/p')&id=1ZP9n79QCIIyA-oAIJRPp13XD1aCHXIJq\" \\\n",
    "           -O checkpoints/vfnet_checkpoint.pth && rm -rf /tmp/cookies.txt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "9ekJARcxJKcR"
   },
   "source": [
    "## 3.2 Блок импорта необходимых библиотек и инициализация переменных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "OcxWQE3DZs6m"
   },
   "outputs": [],
   "source": [
    "import random\n",
    "from time import sleep\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "import cv2\n",
    "import numpy as np\n",
    "from matplotlib import pyplot as plt\n",
    "from PIL import Image, ImageDraw\n",
    "from tqdm import tqdm\n",
    "from google.colab.patches import cv2_imshow\n",
    "from facenet_pytorch import MTCNN, InceptionResnetV1\n",
    "from mmdet.apis import inference_detector, init_detector\n",
    "import mmcv\n",
    "from mmcv import Config\n",
    "from ensemble_boxes import weighted_boxes_fusion\n",
    "\n",
    "SEED = 2021\n",
    "random.seed(SEED)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "WJzSLGvBx0kE",
    "outputId": "f2de6727-c065-4413-b25d-f1a60f48094b"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount(\"/content/drive\", force_remount=True).\n"
     ]
    }
   ],
   "source": [
    "from google.colab import drive\n",
    "drive.mount('/content/drive')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "a6q9r17BQ3RZ"
   },
   "source": [
    "Данные параметры менять нельзя:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "baIe-PW_Q6Wr"
   },
   "outputs": [],
   "source": [
    "# Веса для модели детектора VarifocalNet\n",
    "VFNET_MODEL_WEIGHTS = '/content/mmdetection/checkpoints/vfnet_checkpoint.pth'\n",
    "# Папка с MMDetection, папка создается во время установки\n",
    "MMDET_PATH = '/content/mmdetection'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UPUGKLj8LqfZ"
   },
   "source": [
    "## 3.3 Набор функций для выполнения обработки видео"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "3qFPkaL9U32y"
   },
   "outputs": [],
   "source": [
    "def rectangle_area(box):\n",
    "    \"\"\"Считаем площадь, ширину и высоту бокса\n",
    "    \n",
    "    Параметры\n",
    "    ---------\n",
    "    box : array\n",
    "      Координаты одного бокса в виде массива в формате [x1, y1, x2, y2]\n",
    "\n",
    "    Результат\n",
    "    ---------\n",
    "      area, box_width, box_height\n",
    "    \"\"\"\n",
    "    box_width = box[2] - box[0]\n",
    "    box_height = box[3] - box[1]\n",
    "    area = box_width * box_height\n",
    "\n",
    "    return area, box_width, box_height\n",
    "\n",
    "def normalize_coordinates(x1, y1, x2, y2, img_width, img_height):\n",
    "    \"\"\"Нормализация координат бокса по ширине и высоте изображения\n",
    "    \n",
    "    Параметры\n",
    "    ---------\n",
    "    x1 : int\n",
    "      Координата по оси x верхнего левого угла бокса\n",
    "    y1 : int\n",
    "      Координата по оси y верхнего левого угла бокса\n",
    "    x2 : int\n",
    "      Координата по оси x нижнего правого угла бокса\n",
    "    y2 : int\n",
    "      Координата по оси y нижнего правого угла бокса\n",
    "    img_width : int\n",
    "      Ширина изображения\n",
    "    img_height : высота изображения\n",
    "\n",
    "    Результат\n",
    "    ---------\n",
    "    x1, y1, x2, y2 : (float, float, float, float)\n",
    "      Нормированные координаты бокса\n",
    "    \"\"\"\n",
    "    x1, x2 = x1 / img_width,  x2 / img_width\n",
    "    y1, y2 = y1 / img_height,  y2 / img_height\n",
    "    return x1, y1, x2, y2\n",
    "\n",
    "def denormalize_coordinates(x1, y1, x2, y2, img_width, img_height):\n",
    "    \"\"\"Денормализация координат бокса по ширине и высоте изображения\n",
    "    \n",
    "    Параметры\n",
    "    ---------\n",
    "    x1 : int\n",
    "      Координата по оси x верхнего левого угла бокса\n",
    "    y1 : int\n",
    "      Координата по оси y верхнего левого угла бокса\n",
    "    x2 : int\n",
    "      Координата по оси x нижнего правого угла бокса\n",
    "    y2 : int\n",
    "      Координата по оси y нижнего правого угла бокса\n",
    "    img_width : int\n",
    "      Ширина изображения\n",
    "    img_height : высота изображения\n",
    "\n",
    "    Результат\n",
    "    ---------\n",
    "    x1, y1, x2, y2 : (float, float, float, float)\n",
    "      Денормированные координаты бокса\n",
    "    \"\"\"\n",
    "    x1, x2 = int(x1 * img_width),  int(x2 * img_width)\n",
    "    y1, y2 = int(y1 * img_height),  int(y2 * img_height)\n",
    "    return x1, y1, x2, y2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "WySIaQ9KJiu7"
   },
   "outputs": [],
   "source": [
    "def apply_brightness_contrast(img, brightness=0, contrast=0):\n",
    "    \"\"\"Добавляем яркости и контрастности для изображения\n",
    "    \n",
    "    Параметры\n",
    "    ---------\n",
    "    img : np.array\n",
    "      Изображение\n",
    "    brightness : float\n",
    "      Уровень яркости, по умолчанию 0.\n",
    "    contrast : float\n",
    "      Уровень контрастности, по умолчанию 0.\n",
    "    \n",
    "    Результат\n",
    "    ---------\n",
    "    buf : np.array\n",
    "     Изображение с изменением по яркости и контрастности\n",
    "    \"\"\"\n",
    "    \n",
    "    if brightness != 0:\n",
    "        if brightness > 0:\n",
    "            shadow = brightness\n",
    "            highlight = 255\n",
    "        else:\n",
    "            shadow = 0\n",
    "            highlight = 255 + brightness\n",
    "        alpha_b = (highlight - shadow)/255\n",
    "        gamma_b = shadow\n",
    "        \n",
    "        buf = cv2.addWeighted(img, alpha_b, img, 0, gamma_b)\n",
    "    else:\n",
    "        buf = img.copy()\n",
    "    \n",
    "    if contrast != 0:\n",
    "        f = 131*(contrast + 127)/(127*(131-contrast))\n",
    "        alpha_c = f\n",
    "        gamma_c = 127*(1-f)\n",
    "        \n",
    "        buf = cv2.addWeighted(buf, alpha_c, buf, 0, gamma_c)\n",
    "\n",
    "    return buf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "2uy3nbJJNQe2"
   },
   "outputs": [],
   "source": [
    "def filter_box(boxes, probs, thresh=0.9, area_thr=0):\n",
    "    \"\"\" Фильтрация боксов по площадки и вероятности достоверности класса\n",
    "\n",
    "    Параметры\n",
    "    ---------\n",
    "    boxes : np.array\n",
    "      Матрица боксов. Каждая строка матрицы - отдельный бокс.\n",
    "      Строка имеет формат x1, y1, x2, y2.\n",
    "    probs : np.array\n",
    "      Массив вероятностей для каждого бокса размером 1xN, где N - количество\n",
    "      боксов в boxes\n",
    "    thresh : float\n",
    "      Порог для фильтрации боксов по вероятности из prob. Принимает значения\n",
    "      от 0 до 1. По умолчанию 0.9.\n",
    "    area_thr : float\n",
    "      Площадь бокса для фильтрации. Если площадь бокса будет меньше указанного\n",
    "      значения, то бокс будет удален. По умолчанию 0.\n",
    "\n",
    "    Результат\n",
    "    ---------\n",
    "    filtered_bboxes, filtered_probs, box_params\n",
    "      filtered_bboxes - массив отфильтрованных боксов, если боксов нет, то\n",
    "                        принимает значение None\n",
    "      filtered_probs - массив отфильтрованных вероятностей боксов, если боксов нет, то\n",
    "                       принимает значение None\n",
    "      box_params - массив параметров боксов размером Nx3, где N - количество фильтрованных боксов.\n",
    "                   У каждого бокса перечислены параметры площадки, ширины и высоты\n",
    "    \"\"\"\n",
    "\n",
    "    assert len(boxes) == len(probs)\n",
    "    assert 0.0<=thresh<=1.0\n",
    "\n",
    "    filtered_bboxes = []\n",
    "    filtered_probs = []\n",
    "    box_params = []\n",
    "    \n",
    "    for box, prob in zip(boxes, probs):\n",
    "        x1, y1, x2, y2 = box\n",
    "        b_area, b_width, b_height = [round(float(val), 2) for val in rectangle_area(box)]\n",
    "        # Условия исходят из того, что чем ближе к камере человек, тем больше его лицо и тем ниже располагается бокс\n",
    "        if prob >= thresh and ((y2>=700 and area_thr<=b_area<=31000) or (y2<700 and area_thr<=b_area<13000)):\n",
    "            filtered_bboxes.append(box.tolist())\n",
    "            box_params.append([b_area, b_width, b_height])\n",
    "            filtered_probs.append(prob)\n",
    "\n",
    "    if len(filtered_bboxes) > 0:\n",
    "        filtered_bboxes = np.array(filtered_bboxes)\n",
    "        filtered_probs = np.array(filtered_probs)\n",
    "        box_params = np.array(box_params)\n",
    "    else:\n",
    "        filtered_bboxes = None\n",
    "        filtered_probs = None\n",
    "        box_params = None\n",
    "\n",
    "    return filtered_bboxes, filtered_probs, box_params\n",
    "\n",
    "def draw_box(img, boxes, box_params, model_name=None):\n",
    "    \"\"\" Рисуем боксы и дополнительную информацию на изображении\n",
    "    \n",
    "    Параметры\n",
    "    ---------\n",
    "    img : np.array\n",
    "      Изображение\n",
    "    boxes : np.array\n",
    "      Матрица боксов. Каждая строка матрицы - отдельный бокс.\n",
    "      Строка имеет формат x1, y1, x2, y2.\n",
    "    box_params : np.array\n",
    "    model_name : str\n",
    "      Название модели, от которой получены боксы.\n",
    "      По умолчанию None.\n",
    "\n",
    "    Результат\n",
    "    ---------\n",
    "    img : np.array\n",
    "    \"\"\"\n",
    "\n",
    "    img = img.copy()\n",
    "    for box, box_param in zip(boxes, box_params):\n",
    "        box = box.astype(int)\n",
    "        b_area, b_width, b_height = box_param\n",
    "        cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), (0,0,255), 6)\n",
    "        cv2.putText(img, f\"S: {b_area}\", (int(box[0]), int(box[1])-10), \n",
    "                    cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,255), 2)\n",
    "        cv2.putText(img, f\"W: {b_width}\", (int(box[0]), int(box[1])-40), \n",
    "                    cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,255), 2)\n",
    "        cv2.putText(img, f\"H: {b_height}\", (int(box[0]), int(box[1])-70), \n",
    "                    cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,255), 2)\n",
    "        if model_name:\n",
    "            cv2.putText(img, f\"MODEL: {model_name}\", (int(box[0]), int(box[1])-100), \n",
    "                        cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,255), 2)\n",
    "    return img"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "TLOjVqKDaDWL"
   },
   "outputs": [],
   "source": [
    "def make_vfnet_config(mmdet_path=None):\n",
    "    \"\"\"Создаем конфиг для детектора VarifocalNet\"\"\"\n",
    "\n",
    "    if mmdet_path is None:\n",
    "        mmdet_path = '.'\n",
    "    cfg = Config.fromfile(f'{mmdet_path}/configs/vfnet/vfnet_x101_64x4d_fpn_mdconv_c3-c5_mstrain_2x_coco.py')\n",
    "    cfg.model.bbox_head.num_classes = 2 \n",
    "    cfg.model.test_cfg.nms.iou_threshold = 0.05\n",
    "    cfg.data.train.pipeline[2].img_scale = [(1333, 480), (1333, 960)]\n",
    "    # Модель обучалась на изображениях 1333x960\n",
    "    # Если разрешение теста увеличить до реального изображения из видео,\n",
    "    # то будут плохо определяться классы (в магазине, вне магазина)\n",
    "    # Менять не нужно, фреймворк автоматически подгонит размер бокса под реальный\n",
    "    # размер изображения. А изображение будет сжато только для получения результата.\n",
    "    cfg.data.val.pipeline[1].img_scale = (1333, 960)\n",
    "    cfg.data.test.pipeline[1].img_scale = (1333, 960)\n",
    "    cfg.gpu_ids = range(1)\n",
    "    \n",
    "    return cfg\n",
    "\n",
    "def vfnet_result_split(vfnet_predict):\n",
    "    \"\"\" Разделяем результат модели VarifocalNet\n",
    "    на массив с боксами и массив с вероятностями\n",
    "\n",
    "    Параметры\n",
    "    ---------\n",
    "    vfnet_predict : np.array\n",
    "      Результат функции inference_detector\n",
    "\n",
    "    Результат\n",
    "    ---------\n",
    "    boxes, probs : (np.array, np.array)\n",
    "    \"\"\"\n",
    "    boxes = []\n",
    "    probs = []\n",
    "    # Берем только результаты для класса 1 (IN - в магазине)\n",
    "    vfnet_predict = vfnet_predict[1]\n",
    "    for x1, y1, x2, y2, conf in vfnet_predict:\n",
    "        boxes.append([x1, y1, x2, y2])\n",
    "        probs.append(conf)\n",
    "    if len(boxes) == 0:\n",
    "        boxes = None\n",
    "        probs = None\n",
    "    else:\n",
    "        boxes = np.array(boxes)\n",
    "        probs = np.array(probs)\n",
    "\n",
    "    return boxes, probs\n",
    "\n",
    "def weighted_boxes(mtcnn_boxes, mtcnn_probs, vfnet_boxes, vfnet_probs, image_width,\n",
    "                   image_height, iou_thr=0.5):\n",
    "    \"\"\" Объединяем боксы из моделей MTCNN и VarifocalNet с помощью \n",
    "    Weighted Box Fusion\n",
    "    \n",
    "    Параметры\n",
    "    ---------\n",
    "    mtcnn_boxes : np.array\n",
    "      Матрица боксов изображения от модели MTCNN\n",
    "    mtcnn_probs : np.array\n",
    "      Массив вероятностей боксов изображения от модели MTCNN\n",
    "    vfnet_boxes : np.array\n",
    "      Матрица боксов изображения от модели VarofocalNet\n",
    "    vfnet_probs : np.array\n",
    "      Массив вероятностей боксов изображения от модели VarofocalNet\n",
    "    image_width : int\n",
    "      Ширина изображения\n",
    "    image_height : int\n",
    "      Высота изображения\n",
    "    iou_thr : float\n",
    "      Порог срабатывания по IoU для объединения боксов. По умолчанию 0.5.\n",
    "\n",
    "    Результат\n",
    "    ---------\n",
    "    denorm_w_boxes : np.array\n",
    "      Объединенные боксы\n",
    "    \"\"\"\n",
    "\n",
    "    assert 0<=iou_thr<=1.0\n",
    "    \n",
    "    # Функция weighted_boxes_fusion требует нормализации координато боксов\n",
    "    norm_mtcnn_boxes = [normalize_coordinates(x1, y1, x2, y2, image_width, image_height) \\\n",
    "                        for x1, y1, x2, y2 in mtcnn_boxes]\n",
    "    norm_vfnet_boxes = [normalize_coordinates(x1, y1, x2, y2, image_width, image_height) \\\n",
    "                        for x1, y1, x2, y2 in vfnet_boxes]\n",
    "    \n",
    "    boxes_list = [norm_mtcnn_boxes, norm_vfnet_boxes]\n",
    "    scores_list = [mtcnn_probs.tolist(), vfnet_probs.tolist()]\n",
    "    labels_list = [[1]*len(mtcnn_boxes), [1]*len(vfnet_boxes)]\n",
    "\n",
    "    w_boxes, scores, labels = weighted_boxes_fusion(boxes_list, scores_list, labels_list, \n",
    "                                                    iou_thr=iou_thr, skip_box_thr=0.0001)\n",
    "\n",
    "    denorm_w_boxes = [denormalize_coordinates(x1, y1, x2, y2, image_width, image_height) \\\n",
    "                      for x1, y1, x2, y2 in w_boxes]\n",
    "    denorm_w_boxes = np.array(denorm_w_boxes)\n",
    "\n",
    "    return denorm_w_boxes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LyX6vnUyLewi"
   },
   "source": [
    "# 4 Скрипт для сохранения детекции лиц в исходном видео\n",
    "\n",
    "## 4.1 Входные параметры\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "n7p71coXr8TX"
   },
   "outputs": [],
   "source": [
    "# Видео для анализа\n",
    "VIDEO_PATH = '/content/drive/MyDrive/data_science/FrontDoor.mp4'\n",
    "# В какой файл будет сохранено видео с разметкой (у файла должно быть расширение mp4)\n",
    "TARGET_VIDEO_PATH = '/content/drive/MyDrive/data_science/FrontDoor_with_detection.mp4'\n",
    "\n",
    "# Если True, то будет использован ансамбль MTCNN + VarifocalNet,\n",
    "# иначе только детекции модели MTCNN\n",
    "USE_ENSEMBLE = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ex4ghrLur708"
   },
   "source": [
    "## 4.2 Инициализация моделей"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "X4aNsvk1BioM",
    "outputId": "82e1933b-0293-4f4b-9f0c-7eaef7805a22"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load checkpoint from local path: /content/mmdetection/checkpoints/vfnet_checkpoint.pth\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-02-14 02:26:19,814 - root - INFO - ModulatedDeformConvPack backbone.layer2.0.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,820 - root - INFO - ModulatedDeformConvPack backbone.layer2.1.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,825 - root - INFO - ModulatedDeformConvPack backbone.layer2.2.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,829 - root - INFO - ModulatedDeformConvPack backbone.layer2.3.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,834 - root - INFO - ModulatedDeformConvPack backbone.layer3.0.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,840 - root - INFO - ModulatedDeformConvPack backbone.layer3.1.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,845 - root - INFO - ModulatedDeformConvPack backbone.layer3.2.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,850 - root - INFO - ModulatedDeformConvPack backbone.layer3.3.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,855 - root - INFO - ModulatedDeformConvPack backbone.layer3.4.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,860 - root - INFO - ModulatedDeformConvPack backbone.layer3.5.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,865 - root - INFO - ModulatedDeformConvPack backbone.layer3.6.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,871 - root - INFO - ModulatedDeformConvPack backbone.layer3.7.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,876 - root - INFO - ModulatedDeformConvPack backbone.layer3.8.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,880 - root - INFO - ModulatedDeformConvPack backbone.layer3.9.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,885 - root - INFO - ModulatedDeformConvPack backbone.layer3.10.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,891 - root - INFO - ModulatedDeformConvPack backbone.layer3.11.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,895 - root - INFO - ModulatedDeformConvPack backbone.layer3.12.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,900 - root - INFO - ModulatedDeformConvPack backbone.layer3.13.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,905 - root - INFO - ModulatedDeformConvPack backbone.layer3.14.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,910 - root - INFO - ModulatedDeformConvPack backbone.layer3.15.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,915 - root - INFO - ModulatedDeformConvPack backbone.layer3.16.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,920 - root - INFO - ModulatedDeformConvPack backbone.layer3.17.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,925 - root - INFO - ModulatedDeformConvPack backbone.layer3.18.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,929 - root - INFO - ModulatedDeformConvPack backbone.layer3.19.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,935 - root - INFO - ModulatedDeformConvPack backbone.layer3.20.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,942 - root - INFO - ModulatedDeformConvPack backbone.layer3.21.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,948 - root - INFO - ModulatedDeformConvPack backbone.layer3.22.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,955 - root - INFO - ModulatedDeformConvPack backbone.layer4.0.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,963 - root - INFO - ModulatedDeformConvPack backbone.layer4.1.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,970 - root - INFO - ModulatedDeformConvPack backbone.layer4.2.conv2 is upgraded to version 2.\n",
      "2022-02-14 02:26:19,983 - root - INFO - ModulatedDeformConvPack bbox_head.cls_convs.2.conv is upgraded to version 2.\n",
      "2022-02-14 02:26:19,988 - root - INFO - ModulatedDeformConvPack bbox_head.reg_convs.2.conv is upgraded to version 2.\n"
     ]
    }
   ],
   "source": [
    "# Иницализация детектора архитектуры VarifocalNet\n",
    "vfnet_config = make_vfnet_config(mmdet_path=MMDET_PATH)\n",
    "vfnet_model = init_detector(vfnet_config, VFNET_MODEL_WEIGHTS, device='cuda:0')\n",
    "\n",
    "# Инициализация детектора архитектуры MTCNN\n",
    "thresholds = [0.7, 0.8, 0.8]\n",
    "mtcnn = MTCNN(keep_all=True, \n",
    "              select_largest=False, \n",
    "              thresholds=thresholds, \n",
    "              device='cuda:0',\n",
    "              min_face_size=60)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "a1zFn7ByLh4J"
   },
   "source": [
    "## 4.3 Запуск обработки и сохранения видео\n",
    "\n",
    "К рамке боксов прилагается отладочная информация:\n",
    "- площадь бокса;\n",
    "- высота и ширина бокса;\n",
    "- какой модели бокс принадлежит."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "txgpYgyCCWU_",
    "outputId": "af4f3b06-abf5-4298-9b9b-2574e526a580"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  5%|▌         | 2461/45045 [21:02<6:07:00,  1.93it/s]/usr/local/lib/python3.7/dist-packages/ensemble_boxes/ensemble_boxes_wbf.py:73: UserWarning: X2 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.\n",
      "  warnings.warn('X2 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.')\n",
      " 21%|██        | 9567/45045 [1:21:33<5:06:07,  1.93it/s]/usr/local/lib/python3.7/dist-packages/ensemble_boxes/ensemble_boxes_wbf.py:85: UserWarning: Y2 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.\n",
      "  warnings.warn('Y2 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.')\n",
      " 68%|██████▊   | 30562/45045 [4:19:25<2:05:36,  1.92it/s]"
     ]
    }
   ],
   "source": [
    "# Инициализируем захват видео\n",
    "v_cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "# Определяем ширину и высоту кадра\n",
    "frame_width = int(v_cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n",
    "frame_height = int(v_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n",
    "# Определяем FPS источника видео\n",
    "fps = int(v_cap.get(cv2.CAP_PROP_FPS))\n",
    "# Инициализируем объект для записи видео\n",
    "fourcc = cv2.VideoWriter_fourcc(*'FMP4')\n",
    "v_writer = cv2.VideoWriter(TARGET_VIDEO_PATH, fourcc, fps, \n",
    "                           (frame_width, frame_height), True)\n",
    "\n",
    "assert isinstance(frame_width, int)\n",
    "assert isinstance(frame_height, int)\n",
    "assert isinstance(fps, int)\n",
    "\n",
    "try:\n",
    "    # Цикл по каждому кадру видео\n",
    "    for idx in tqdm(range(int(v_cap.get(cv2.CAP_PROP_FRAME_COUNT)))):\n",
    "\n",
    "        # Считываем текущий кадр\n",
    "        success, frame = v_cap.read()\n",
    "        # Прерываем цикл в случае ошибки чтения кадра\n",
    "        if not success:\n",
    "            break\n",
    "\n",
    "        mtcnn_img = Image.fromarray(cv2.cvtColor(frame.copy(), cv2.COLOR_BGR2RGB))\n",
    "        if USE_ENSEMBLE:\n",
    "            vfnet_img = mmcv.imread(frame)\n",
    "\n",
    "        # Получаем результат детекции модели MTCNN\n",
    "        mtcnn_boxes, mtcnn_probs = mtcnn.detect(mtcnn_img)\n",
    "        # Получаем результат детекции модели VarifocalNet\n",
    "        if USE_ENSEMBLE:\n",
    "            vfnet_boxes, vfnet_probs = vfnet_result_split(inference_detector(vfnet_model, vfnet_img))\n",
    "\n",
    "        # Если найдены боксы в модели mtcnn, то выполняем их фильтрацию\n",
    "        if mtcnn_boxes is not None:\n",
    "            mtcnn_boxes, mtcnn_probs, mtcnn_box_params = filter_box(mtcnn_boxes, mtcnn_probs, thresh=0.9, area_thr=8000)\n",
    "        # Если найдены боксы в модели vfnet, то выполняем их фильтрацию\n",
    "        if vfnet_boxes is not None and USE_ENSEMBLE:\n",
    "            vfnet_boxes, vfnet_probs, vfnet_box_params = filter_box(vfnet_boxes, vfnet_probs, thresh=0.7, area_thr=1000)\n",
    "\n",
    "        # Здесь происходит объединение результатов двух моделей\n",
    "        if USE_ENSEMBLE and vfnet_boxes is not None and mtcnn_boxes is not None:\n",
    "            result_boxes = weighted_boxes(mtcnn_boxes, mtcnn_probs, vfnet_boxes, vfnet_probs, \n",
    "                                        iou_thr=0.05, image_width=frame_width, \n",
    "                                        image_height=frame_height)\n",
    "            result_box_params = np.array([rectangle_area(box) for box in result_boxes])\n",
    "            model_name = 'ENSEMBLE'\n",
    "        elif USE_ENSEMBLE and vfnet_boxes is not None:\n",
    "            result_boxes = vfnet_boxes\n",
    "            result_box_params = vfnet_box_params\n",
    "            model_name = 'VFNET'\n",
    "        elif mtcnn_boxes is not None:\n",
    "            result_boxes = mtcnn_boxes\n",
    "            result_box_params = mtcnn_box_params\n",
    "            model_name = 'MTCNN'\n",
    "        else:\n",
    "            result_boxes = None\n",
    "            \n",
    "        # Применяем результаты детекции к кадру\n",
    "        if result_boxes is not None:\n",
    "            frame = draw_box(frame, result_boxes, result_box_params, model_name=model_name)\n",
    "                \n",
    "        v_writer.write(frame)\n",
    "\n",
    "finally:\n",
    "    v_writer.release()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ITDSGlyODzUv"
   },
   "source": [
    "# 5 Результат обработки\n",
    "\n",
    "Видео с результатами обработки можно посмотреть здесь:\n",
    "\n",
    "https://drive.google.com/file/d/1WI5wwyq8jq9LcTuvzx0iDF_3s2GuTxoB/view?usp=sharing\n",
    "\n",
    "\n",
    "# 6 Какие подходы не сработали\n",
    "- Связка основной модели и трекера, после первой детекции лица начинаем его плавный трекинг (у основной модели есть ложные срабатывания, есть проблема с удалением рамки после пропадания лица из кадра);\n",
    "- Увеличение яркости и контракстности для основной модели (качество не изменилось);\n",
    "- Увеличение разрешения изображения для основной модели (качество не изменилось);\n",
    "- Предсказания дополнительной модели на разрешении в 2 раза большем, чем при обучении (ухудшилось качество классификации)\n",
    "\n",
    "# 7 Что можно улучшить\n",
    "\n",
    "- обучить дополнительную модель на большем количестве примеров лиц в магазине;\n",
    "- улучшить качество разметки для дополнительной модели (более точные грани лица с минимальным захватом посторонних элементов на улице и в магазине);\n",
    "- установить в ручной разметке класс IN (в магазине) для дополнительной модели после того, как человек перешел порог (сейчас класс IN определяется для человека на пороге, это место неоднозначное, есть ложные срабатыванния за порогом);\n",
    "- обучить дополнительную модель на большом размеченном датасете с лицами (желательно, чтобы были лица в масках, добавить к этому датасету изображения ручной разметки из видео);\n",
    "- попробовать детектировать не лицо, а фигуру человека (например, с помощью Detectron2);\n",
    "- для ускорения вычислений можно взять в качестве дополнительной более легковесную модель (например, Yolov3);\n",
    "- поставить в магазин две камеры для возможности генерации карты глубины."
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "background_execution": "on",
   "collapsed_sections": [],
   "machine_shape": "hm",
   "name": "shop_face_detection.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
